package com.xu.cloud.common.trans;

import com.xu.cloud.common.aop.ServletRequestPointcut;
import com.xu.cloud.common.aop.XuApiPointcut;
import com.xu.cloud.common.api.ApiContext;
import com.xu.cloud.common.api.XuApiMethod;
import com.xu.cloud.common.constant.LogConstant;
import com.xu.cloud.common.exception.ErrorCodeEnum;
import com.xu.cloud.common.exception.XuCloudException;
import com.xu.cloud.common.pojo.Result;
import com.xu.cloud.common.pojo.http.req.BaseRequest;
import com.xu.cloud.common.pojo.http.resp.BaseResponse;
import com.xu.cloud.common.util.HttpServletUtil;
import com.xu.cloud.common.util.ResourceUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.MDC;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 交易参数上下文aop拦截器
 *
 * @author xuguan
 * @since 2024/9/24
 */
public class TransContextAdvisingBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        super.setBeanFactory(beanFactory);
        final Pointcut requestPc = new ServletRequestPointcut();
        final Pointcut xuApiPc = new XuApiPointcut();
        final ComposablePointcut composablePointcut = new ComposablePointcut(requestPc).union(xuApiPc);
        this.advisor = new DefaultPointcutAdvisor(composablePointcut, new TransContextMethodInterceptor());
    }

    private static class TransContextMethodInterceptor implements MethodInterceptor {

        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            skipStatic(methodInvocation);

            final ApiContext apiContext = ApiContext.current();
            final Method method = methodInvocation.getMethod();
            final Object[] args = methodInvocation.getArguments();
            final XuApiMethod apiInfo = AnnotationUtils.findAnnotation(method, XuApiMethod.class);
            if (Objects.nonNull(apiInfo) && StringUtils.hasText(apiInfo.apiCode())) {
                final String apiCode = apiInfo.apiCode();
                apiContext.setApiCode(apiCode);
                MDC.put(LogConstant.API_CODE, apiCode);
            }

            // 请求参数
            for (final Object arg : args) {
                if (Objects.isNull(arg)) {
                    continue;
                }

                if (BaseRequest.class.isAssignableFrom(arg.getClass())) {
                    BaseRequest baseRequest = (BaseRequest) arg;

                    final String reqJournalNo = StringUtils.hasText(baseRequest.getReqJournalNo())
                            ? baseRequest.getReqJournalNo()
                            : apiContext.getReqJournalNo();

                    apiContext.setBaseRequest(baseRequest);
                    apiContext.setReqJournalNo(reqJournalNo);

                    baseRequest.setReqTime(apiContext.getReqTime());
                    baseRequest.setReqJournalNo(apiContext.getReqJournalNo());
                }
            }

            MDC.put(LogConstant.REQUEST_JOURNAL_NO, apiContext.getReqJournalNo());

            // 请求执行
            Object returnObj = null;
            try {
                returnObj = methodInvocation.proceed();

                apiContext.setCode(ErrorCodeEnum.OK.getCode());
                apiContext.setMsg(ErrorCodeEnum.OK.getMessage());
            } catch (XuCloudException xuCloudException) {
                final String code = xuCloudException.getCode();
                final String message = xuCloudException.getMessage();
                apiContext.setThrowable(xuCloudException);
                apiContext.setCode(code);
                apiContext.setMsg(message);

                throw xuCloudException;
            } catch (Throwable throwable) {
                apiContext.setThrowable(throwable);
                apiContext.setCode(ErrorCodeEnum.INTERNAL_SERVER_ERROR.getCode());
                apiContext.setMsg(ErrorCodeEnum.INTERNAL_SERVER_ERROR.getMessage());

                throw throwable;
            } finally {
                // 后置处理
                Object data = null;
                BaseResponse baseResponse = null;

                if (Objects.nonNull(returnObj)) {
                    if (Result.class.isAssignableFrom(returnObj.getClass())) {
                        Result<?> result = (Result<?>) returnObj;
                        data = result.getData();

                        if (Objects.nonNull(data) && BaseResponse.class.isAssignableFrom(data.getClass())) {
                            baseResponse = (BaseResponse) data;
                        }
                    }

                    if (BaseResponse.class.isAssignableFrom(returnObj.getClass())) {
                        baseResponse = (BaseResponse) returnObj;
                    }
                }

                apiContext.setBaseResponse(baseResponse);
            }

            return returnObj;
        }

        private void skipStatic(MethodInvocation methodInvocation) throws Throwable {
            final String requestUri = HttpServletUtil.currentRequest().getRequestURI();
            if (ResourceUtil.isStatic(requestUri)) {
                methodInvocation.proceed();
            }
        }
    }
}
